package lk.icta.core.service.impl;

import lk.icta.common.ELandRuntimeException;
import lk.icta.common.ErrorCodes;
import lk.icta.common.entity.Child;
import lk.icta.common.util.CommonUtil;
import lk.icta.core.dao.*;
import lk.icta.core.dto.ArrearsRecordDetail;
import lk.icta.core.dto.LeaseArrearsDetails;
import lk.icta.core.entity.*;
import lk.icta.core.service.AlienationRailwayManagementService;
import lk.icta.user.common.Permission;
import lk.icta.user.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;


public class AlienationRailwayManagementServiceImpl implements AlienationRailwayManagementService {

    private static final Logger logger = LoggerFactory.getLogger(AlienationRailwayManagementServiceImpl.class);

    private final AlienationRailwayDAO alienationRailwayDAO;
    private final OccupierDAO occupierDAO;
    private final ReportsRailwayDAO reportsRailwayDAO;

    public AlienationRailwayManagementServiceImpl(AlienationRailwayDAO alienationRailwayDAO, OccupierDAO  occupierDAO, ReportsRailwayDAO reportsRailwayDAO) {
        this.alienationRailwayDAO = alienationRailwayDAO;
        this.occupierDAO =  occupierDAO;;
        this.reportsRailwayDAO = reportsRailwayDAO ;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addAlienation(AlienationRailway alienation, User user) {
        logger.debug("Adding an AlienationRailway  by {}", user.getUsername());
        if (user.isAuthorized(Permission.INITIAL_ALIENATION_DATA_ENTRY)) {

            alienation.setApproveState(AlienationRailway.State.DATA_ENTRY);

            alienationRailwayDAO.addAlienation(alienation, user);
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to add an Alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }



    @Transactional(propagation = Propagation.REQUIRED)
    public void updateAlienation(AlienationRailway alienation, User user) {
        logger.debug("Updating - AlienationRailway {} by {}", alienation.getId(),
                user.getUsername());
        if (user.isAuthorized(Permission.DETAIL_ALIENATION_DATA_MODIFICATION)) {

            alienation.setApproveState(AlienationRailway.State.DATA_ENTRY);

            if (alienation.getId() != 0) {

                AlienationRailway existingAlienation = alienationRailwayDAO.getAlienationById(alienation.getId(), user);
    //todo: There is an exception when updating the land with a many occupiers. Need to debug when a occupier in a middle has been deleted
                if (existingAlienation != null) {
                    if (AlienationRailway.State.ARCHIVED.equals(existingAlienation.getApproveState())) {
                        handleELandRuntimeException("Attempt to update an archived alienation",
                                ErrorCodes.ARCHIVED_ALIENATION_UPDATING);
                    }
                    List<Occupier> newOccupiers = alienation.getOccupiers();
                    List<Occupier> oldOccupiers = existingAlienation.getOccupiers();
                    boolean matchFound= false;

                    for (Occupier oldOcc : oldOccupiers) {
                        matchFound = false;
                        if (newOccupiers != null) {
                            for (Occupier newOcc : newOccupiers) {
                                if (newOcc == null) {
                                    matchFound = false;
                                } else if (oldOcc.getId() == newOcc.getId()) {
                                    matchFound = true;
                                    break;
                                }
                            }
                        }
                        if (!matchFound) {
                            occupierDAO.removeOccupier(oldOcc, user);
                        }
                    }
                    alienation.setMigrationState(existingAlienation.getMigrationState());
                    alienation.getLifeCycleInfo().setCreatedUser(existingAlienation.getLifeCycleInfo().getCreatedUser());
                    alienation.getLifeCycleInfo().setCreatedTimeStamp(existingAlienation.getLifeCycleInfo().getCreatedTimeStamp());
                    alienationRailwayDAO.updateAlienation(alienation, user);
                }
            } else {
                alienation.setMigrationState(AlienationRailway.MigrationState.NONE);
                alienationRailwayDAO.addAlienation(alienation, user);
            }
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to update detail alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }


    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public AlienationRailway getActiveAlienationByLandId(long landId, User user) {
        logger.debug("getActiveAlienationByLandId {}", landId);
        AlienationRailway alienation = alienationRailwayDAO.getActiveAlienationByLandId(landId, user);
        if (alienation != null && alienation.getHolder() != null && alienation.getHolder().getChildren() != null) {
            // alienation.setHolder(alienation.getHolder());
            alienation.getHolder().setChildren(alienation.getHolder().getChildren());

            for (Child c : alienation.getHolder().getChildren()) {
                logger.debug("Childrn name is -- {}", c.getFullName());
            }
        }

        return alienation;
    }

    @Override
    public List<AlienationRailway> getAlienationByReferenceNo(String referenceNo, User user) {
        return alienationRailwayDAO.getAlienationRailwayByLeaseId(user,referenceNo);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<LeaseArrearsDetails> getAlienationByLandDistrictBranchTrack(int districtId,int branchId,int railwayTrackId,double arrearsMax,double arrearsMin,  User user){
        List<LeaseArrearsDetails> leaseArrearsDetails = reportsRailwayDAO.getLeaseArrearsReport(districtId,branchId,railwayTrackId,arrearsMax,arrearsMin, user);
        return leaseArrearsDetails;
    }

    @Override
    public List<Object[]> AlienationStatisticsReportData(int branchId, int trackId, int districtId, String fromDate, String toDate, double fromValue, double toValue, User user, String language) {
        Date startDate = CommonUtil.getDate(fromDate);
        Date endDate = CommonUtil.getDate(toDate);
        if(endDate == null) { endDate = new Date(); } // if end date isn't provided get today
        return alienationRailwayDAO.getAlienationStatisticsData(branchId, trackId, districtId, startDate, endDate, fromValue, toValue);

    }

    @Override
    public List<AlienationRailway> getDetailedLeaseList(int branchId, int railwayTrackId, int districtId, int startingVal, int endingVal, String startingDate, String endingDate, User user, String language) {
        Date startDate = CommonUtil.getDate(startingDate);
        Date endDate = CommonUtil.getDate(endingDate);
        if(endDate == null) { endDate = new Date(); } // if end date isn't provided get today
        return alienationRailwayDAO.getAlienationDetailsList(branchId, railwayTrackId, districtId, startDate, endDate, startingVal, endingVal);
    }

    @Override
    public List<AlienationRailway> getArrearsStatisticsReportData(int branchId, int trackId, int districtId, long minAmnt, long maxAmnt) {
       return alienationRailwayDAO.getAlienationStatisticsByFilters(branchId, trackId, districtId, minAmnt, maxAmnt);
    }

    @Override
    public List<ArrearsRecordDetail> getArrearsRecordList(int branchId, int railwayTrackId, int districtId, int yearsBack) {
        List<AlienationRailway> list = alienationRailwayDAO.getAlienationRailwayByFilters(branchId, railwayTrackId, districtId);
        List<ArrearsRecordDetail> arrearsRecordList = new ArrayList<ArrearsRecordDetail>();

        ArrearsRecordDetail ad;
       List<ArrearsHistoryRecord> ahs;
        int cYear = Calendar.getInstance().get(Calendar.YEAR);
        int c;
        for(AlienationRailway r :list){
            c = cYear - yearsBack;
            ahs = r.getArrearsHistoryRecords();
            Collections.sort(ahs, new RecordYearComparator());
            ArrearsHistoryRecord ahr;
            double tot = 0;
            double mTot = 0;
            Map<Integer, Double> m = new TreeMap<Integer, Double>();
            int len = (ahs.size() > yearsBack ? ahs.size() : yearsBack);
            for(int i = 0; i< len; i++){
                ahr = (i < ahs.size()) ? ahs.get(i) : null;
                if (ahr != null && ahr.getYear() == cYear){
                    len += ahs.size() <= yearsBack ? 1 : 0;
                    continue;
                }else{
                    tot += ahr != null ? ahr.getArrearsAmount(): 0d;
                    if(ahr != null){
                        if (ahr.getYear() >= c){
                            m.put(ahr.getYear(), ahr.getArrearsAmount());
                        }else{
                            mTot += ahr.getArrearsAmount();
                        }
                    }else{
                        m.put(c, 0d);
                        c++;
                    }
                }
            }

            ad = new ArrearsRecordDetail();
            ad.setFileNo(r.getLand().getLandFileNumber());
            ad.setLeaseNo(r.getReferenceNo());
            ad.setTotalArrears(tot);
            ad.setSubTotalArrears(mTot);
            ad.setArrearsRecords(m);
            arrearsRecordList.add(ad);
        }
        return arrearsRecordList;
    }

    @Override
    public List<AlienationRailway> getAlienationAvailableLikeLeaseNo(String referenceNo, User user) {
       return alienationRailwayDAO.getAlienationLikeReferenceNo(referenceNo);
    }

    private void handleELandRuntimeException(String message, int code) {
        logger.error(message);
        throw new ELandRuntimeException(message, code);
    }

    class RecordYearComparator implements Comparator<ArrearsHistoryRecord> {

        @Override
        public int compare(ArrearsHistoryRecord a, ArrearsHistoryRecord b) {
            return a.getYear() - b.getYear();
        }
    }
}